from socket import socket
from socketserver import BaseRequestHandler, ThreadingTCPServer
import os

CR = ord(b'\r')
LF = ord(b'\n')
SP = ord(b' ')
HT = ord(b'\t')
COLON = ord(b':')

STATUS_CODE_MAP = {
    100: "Continue",
    101: "Switching Protocols",
    200: "OK",
    201: "Created",
    202: "Accepted",
    203: "Non-Authoritative Information",
    204: "No Content",
    205: "Reset Content",
    206: "Partial Content",
    300: "Multiple Choices",
    301: "Moved Permanently",
    302: "Found",
    303: "See Other",
    304: "Not Modified",
    305: "Use Proxy",
    307: "Temporary Redirect",
    400: "Bad Request",
    401: "Unauthorized",
    402: "Payment Required",
    403: "Forbidden",
    404: "Not Found",
    405: "Method Not Allowed",
    406: "Not Acceptable",
    407: "Proxy Authentication Required",
    408: "Request Time-out",
    409: "Conflict",
    410: "Gone",
    411: "Length Required",
    412: "Precondition Failed",
    413: "Request Entity Too Large",
    414: "Request-URI Too Large",
    415: "Unsupported Media Type",
    416: "Requested range not satisfiable",
    417: "Expectation Failed",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Time-out",
    505: "HTTP Version not supported",
}


class Header:
    def __init__(self, name=None, value=None):
        self.name = name
        self.value = value


class Request:
    def __init__(self):
        self.method = None
        self.uri = None
        self.protocol = None
        self.headers = []
        self.body = None

    def __str__(self):
        header_str = ""
        for header in self.headers:
            header_str += f"{header.name}: {header.value}\r\n"
        return f"""{self.method} {self.uri} {self.protocol}
{header_str}
        """.strip()

    def getContentLengthLong(self):
        length = -1
        header = self.getHeader("content-length")
        if header:
            length = int(header.value)
        return length

    def getHeader(self, headerName):
        for header in self.headers:
            if header.name == headerName:
                return header
        return None


class Response:
    def __init__(self):
        self.status = 200
        self.message = None
        self.headers = []
        self.body = None

    def setContentType(self, content_type):
        header = self.getHeader("content-type")
        if header:
            header.value = content_type
        else:
            self.headers.append(Header("content-type", content_type))

    def setStringBody(self, body: str):
        self.setBody(body.encode('utf8'))

    def setBody(self, body):
        if not body: return
        header = self.getHeader("content-length")
        if header:
            header.value = str(len(body))
        else:
            self.headers.append(Header("content-length", str(len(body))))
        self.body = body

    def getHeader(self, headerName):
        for header in self.headers:
            if header.name == headerName:
                return header
        return None


class InputBuffer:
    def __init__(self, client: socket, request: Request):
        self.inputStream = client
        self.request = request
        self.pos = 0
        self.lastValid = 0
        self.buf = bytearray(48 * 1024)

    def parseRequestLine(self):
        space = False
        start = self.pos
        while not space:
            self.fill()
            if self.buf[self.pos] == SP:
                space = True
            self.pos += 1
        self.request.method = self.buf[start:self.pos - 1].decode(encoding='ascii')

        space = False
        start = self.pos
        while not space:
            self.fill()
            if self.buf[self.pos] == SP:
                space = True
            self.pos += 1
        self.request.uri = self.buf[start:self.pos - 1].decode(encoding='ascii')

        start = self.pos
        end_of_line = False
        while not end_of_line:
            self.fill()
            if self.buf[self.pos] == CR:
                pass
            elif self.buf[self.pos] == LF:
                end_of_line = True
            else:
                pass
            self.pos += 1
        self.request.protocol = self.buf[start:self.pos - 2].decode(encoding='ascii')

    def parseHeaders(self):
        while (self.parseHeader()):
            pass

    def parseHeader(self):
        while True:
            self.fill()
            _chr = self.buf[self.pos]
            if _chr == CR or _chr == LF:
                if _chr == LF:
                    self.pos += 1
                    return False
            else:
                break
            self.pos += 1
        header = Header()
        self.request.headers.append(header)
        colon = False
        start = self.pos
        while not colon:
            self.fill()
            if self.buf[self.pos] == COLON:
                colon = True
                header.name = self.buf[start:self.pos].decode(encoding='ascii').lower()
            self.pos += 1
        space = True
        while space:
            self.fill()
            if self.buf[self.pos] == SP or self.buf[self.pos] == HT:
                self.pos += 1
            else:
                space = False

        start = self.pos
        end = 0
        end_of_line = False
        while not end_of_line:
            self.fill()
            if self.buf[self.pos] == CR:
                end = self.pos
            elif self.buf[self.pos] == LF:
                end_of_line = True
            self.pos += 1
        header.value = self.buf[start:end].decode(encoding='utf8')
        return True

    def parseRequestBody(self):
        contentLength = self.request.getContentLengthLong()
        if contentLength <= 0:
            return
        self.fill()
        body = self.buf[self.pos:self.pos + contentLength]
        self.request.body = body

    def fill(self) -> bool:
        if self.pos < self.lastValid:
            return False
        read_bytes = self.inputStream.recv(1024)
        if len(read_bytes) <= 0:
            return False
        # todo 溢出问题
        self.buf[self.pos:self.pos + len(read_bytes)] = read_bytes
        self.lastValid = self.pos + len(read_bytes)
        return True


class OutputBuffer:
    def __init__(self, client: socket, response: Response):
        self.outputStream = client
        self.response = response
        self.pos = 0
        self.buf = bytearray(48 * 1024)

    def end(self):
        self.writeResponse()
        self.outputStream.send(self.buf[0:self.pos])

    def writeResponse(self):
        self.writeStatusLine()
        self.writeHeader()
        self.writeCRLF()
        if self.response.body:
            self.writeBody()

    def writeStatusLine(self):
        self.writeString(f'HTTP/1.1 {self.response.status} {STATUS_CODE_MAP[self.response.status]}')
        self.writeCRLF()

    def writeHeader(self):
        for header in self.response.headers:
            self.writeString(f'{header.name}: {header.value}')
            self.writeCRLF()

    def writeCRLF(self):
        self.buf[self.pos] = CR
        self.pos += 1
        self.buf[self.pos] = LF
        self.pos += 1
        pass

    def writeBody(self):
        self.writeBytes(self.response.body)

    def writeString(self, msg):
        bs = msg.encode('ascii')
        self.writeBytes(bs)

    def writeBytes(self, bs):
        if not bs: return
        self.buf[self.pos:self.pos + len(bs)] = bs
        self.pos += len(bs)


class HttpHandler:
    def __init__(self, client, server):
        self.client = client
        self.server = server
        self.request = Request()
        self.response = Response()
        self.inputBuffer = InputBuffer(client, self.request)
        self.outputBuffer = OutputBuffer(client, self.response)

    def run(self):
        try:
            self.inputBuffer.parseRequestLine()
            self.inputBuffer.parseHeaders()
            self.prepareAndCheckRequest()
            self.inputBuffer.parseRequestBody()
            self.prepareResponse()
            self.server.adapter.service(self.request, self.response)
        except Exception as e:
            # todo other http exception
            print(e)
            self.response.status = 500
        finally:
            self.outputBuffer.end()
            # todo close socket

    def prepareAndCheckRequest(self):
        # todo
        pass

    def prepareResponse(self):
        self.response.headers.append(Header("server", "edfeff"))


class SimpleHttpServerHandler(BaseRequestHandler):
    def handle(self):
        handler_task = HttpHandler(self.request, self.server)
        handler_task.run()

    def finish(self) -> None:
        # print("finish", self.request)
        pass


class IAdapter:
    def service(self, req: Request, res: Response):
        print(req)
        pass


class EchoAdapter(IAdapter):
    def service(self, req: Request, res: Response):
        req_str = str(req)
        res.setStringBody(req_str)
        res.setContentType("text/plain")


class FileAdapter(IAdapter):
    def service(self, req: Request, res: Response):
        file_path = self.getFilePath(req.uri)
        if os.path.exists(file_path):
            with open(file_path, 'rb') as f:
                res.setBody(f.read())
        else:
            res.status = 404
            res.setStringBody("file not exists.")

    def getFilePath(self, uri: str):
        if uri.startswith("/"):
            uri = uri[1:]
        if "" == uri:
            uri = "index.html"
        current_file_path = os.path.abspath(__file__)
        parent_directory = os.path.dirname(current_file_path)
        file_path = os.path.join(parent_directory, "..", "resources", "static", uri)
        return file_path


class SimpleHttpServer:
    def __init__(self, port, handler, adapter):
        self.server = None
        self.server_address = ('127.0.0.1', port)
        self.handler = handler
        self.adapter = adapter

    def start(self):
        self.server = ThreadingTCPServer(self.server_address, self.handler)
        self.server.adapter = self.adapter
        self.server.serve_forever()


if __name__ == '__main__':
    print("服务启动...")
    SimpleHttpServer(port=8080,
                     handler=SimpleHttpServerHandler,
                     # adapter=EchoAdapter(),
                     adapter=FileAdapter(),
                     ).start()
